#include <QTextStream>
#include <QFile>
#include <QTextCodec>
#include <QDebug>
#include <QThread>
#include <QRegExp>
#include <QStringList>
#include <QtEndian>
#include <QFileInfo>
#include <QDir>
#include "rconstdifinition.h"
#include "rengine.h"
#include "rumdfilemodel.h"

RUmdFileModel::RUmdFileModel(REngine& engine, QObject *parent):
    RFileModel(engine,parent)
{
    m_file_length = 0;
    m_fixed_len = 0;
    m_chapter_offset_value = 0;
    m_chapter_value = 0;
    m_cover_type = 0;
    m_cover_value = 0;
    m_content_value = 0;
    m_uncompressed_file = NULL;
    m_file_analyzed = false;
}

RUmdFileModel::~RUmdFileModel() {
    if (m_uncompressed_file) {
        m_uncompressed_file->close();
        delete m_uncompressed_file;
    }
}

bool RUmdFileModel::analyzeFile() {
    bool result = false;
    qDebug()<<"analyzeFile"<<m_file->fileName();
    if (!m_file_analyzed) {
        m_file_analyzed = true;
        if (m_file->isReadable()) {
            //seek to the beginning of file
            if (m_file->seek(0)) {
                //System codec could not use QTextStream to get pos
                qDebug()<<"Analyse with file"<<m_file->fileName();
                qDebug()<<"currentThreadId"<<QThread::currentThreadId();

                //Read umd file key
                QByteArray array = m_file->read(KUmdFileKeyLen);
                if (array.length()) {
                    quint32 fileKey = m_engine.byteArrayToUint(array);
                    if (fileKey == KUmdFileKey) {
                        while(1) {
                            array = m_file->read(KUmdKeyLen);
                            if (array.length() == 0) {
                                //No more data, break
                                break;
                            }
                            quint32 key = m_engine.byteArrayToUint(array);
                            switch (key) {
                            case FUNCTION_KEY:
                                result = processFunction();
                                break;

                            case DATA_KEY:
                                result = processData();
                                break;

                            default:
                                result = false;
                                break;
                            }
                        }
                        processChapterOffset();
                        processChapterString();
                        if (m_file_line_pos.count() == 0) {
                            processContent();
                        }
                    }
                    else {
                        qDebug()<<"ERROR::Not a valid UMD file";
                        result = false;
                    }
                }
                else {
                    qDebug()<<"ERROR::Could not read file";
                    result = false;
                }
            }
            else {
                qDebug()<<"ERROR::Could not seek file to 0";
            }
        }
        else {
            qDebug()<<"ERROR::file is not readable";
        }
    }
    else {
        qDebug()<<"File is already analyzed, do nothing";
        result = true;
    }
    return result;
}

bool RUmdFileModel::processData() {
    QByteArray dataHead = m_file->read(KUmdDataHeadLen);
    quint32 value = m_engine.byteArrayToUint(dataHead.left(KUmdDataValueLen));
    //need to minus KUmdDataHeadFullLen
    quint32 length = m_engine.byteArrayToUint(dataHead.mid(KUmdDataValueLen, KUmdDataLengthLen)) - KUmdDataHeadFullLen;
    QByteArray data = m_file->read(length);
    m_data[value] = data;
    return true;
}

bool RUmdFileModel::processFunction() {
    QByteArray funcHead = m_file->read(KUmdFuncHeadLen);
    quint32 umdTag = m_engine.byteArrayToUint(funcHead.left(KUmdFuncCategoryLen));
    //need to minus KUmdTagHeadLen
    quint32 length = m_engine.byteArrayToUint(funcHead.right(KUmdFuncLengthLen)) - KUmdFuncHeadFullLen;
    QByteArray content = m_file->read(length);
    if (content.length()) {
        switch (umdTag) {
        case VERSION:
            //First byte is the version
            m_version = content[0];
            if (m_version != TEXT) {
                return false;
            }
            break;

        case TITLE:
            m_title = m_codec->toUnicode(content);
            break;

        case AUTHOR:
            m_author = m_codec->toUnicode(content);
            break;

        case YEAR:
            m_year = m_codec->toUnicode(content);
            break;

        case MONTH:
            m_month = m_codec->toUnicode(content);
            break;

        case DAY:
            m_day = m_codec->toUnicode(content);
            break;

        case GENDER:
            m_gender = m_codec->toUnicode(content);
            break;

        case PUBLISHER:
            m_publisher = m_codec->toUnicode(content);
            break;

        case VENDOR:
            m_vendor = m_codec->toUnicode(content);
            break;

        case CONTENT_LENGTH: //content length
            //Do nothing now
            break;

        case FIXED_LEN:
            //Do nothing now
            break;

        case REF_CONTENT: //ZLib compressed content referrence
            if (content.length() == KUmdContentTotalLen) {
                m_content_value = m_engine.byteArrayToUint(content.left(KUmdContentValueLen));
            }
            break;

        case COVER_PAGE:
            if (content.length() == KUmdCoverTotalLen) {
                //First byte is cover type
                m_cover_type =  m_engine.byteArrayToUint(content.left(KUmdCoverTypeLen));
                //Next 4 bytes is cover value
                m_cover_value = m_engine.byteArrayToUint(content.mid(KUmdCoverTypeLen,KUmdCoverValueLen));
            }
            break;

        case CHAP_OFF: //Chapter offset
            if (content.length() == KUmdChapterOffTotalLen) {
                m_chapter_offset_value = m_engine.byteArrayToUint(content.left(KUmdChapterOffValueLen));
            }
            break;

        case CHAP_STR: //Chapter string
            if (content.length() == KUmdChapterTotalLen) {
                m_chapter_value = m_engine.byteArrayToUint(content.left(KUmdChapterValueLen));
            }
            break;

        case PAGE_OFFSET:
            //Do not handle page offset right now
            break;

        case CONTENT_ID:
            //Do nothing
            break;

        case CDS_KEY:
            //Do nothing
            break;

        case LICENSE_KEY:
            //Do nothing
            break;

        default:
            break;
        }
    }
    return true;
}

QByteArray RUmdFileModel::uncompressContent(QByteArray& content) {
    //Need to add content length to the beginning of content before uncompress
    QByteArray tmp = content;
    quint32 bigEndianLen = qToBigEndian<quint32>((quint32)content.length());
    tmp.insert(0, (const char*)&bigEndianLen, sizeof(bigEndianLen));
    return qUncompress(tmp);
}

bool RUmdFileModel::processContent() {
    bool result = false;
    QList<quint32> contentValueList;
    //First check content data value
    if (m_content_value && m_data.contains(m_content_value)) {
        QByteArray content = m_data[m_content_value];
        //Get content data value list
        for (int i = 0; i < content.length()/KUmdDataContentValueLen; i++) {
            contentValueList.append(m_engine.byteArrayToUint(content.mid(i*KUmdDataContentValueLen, KUmdDataContentValueLen)));
        }

        QByteArray totalContent;
        //Get real Zlib compressed content data
        for (int i = 0; i <contentValueList.count(); i++) {
            if (contentValueList[i] && m_data.contains(contentValueList[i])) {
                //Uncompress the content
                QByteArray uncompressedContent = uncompressContent(m_data[contentValueList[i]]);
                //                QString uncompStr = m_codec->toUnicode(uncompressedContent);
                totalContent.append(uncompressedContent);
            }
        }

        //Replace UMD newline with UTF16LE newline
        QByteArray umdNewLine = QByteArray::fromRawData((const char*)&KNewLineCodeUmdLe, sizeof(KNewLineCodeUmdLe));
        QByteArray utf16LeNewLine = QByteArray::fromRawData((const char*)&KNewLineCodeUtf16Le, sizeof(KNewLineCodeUtf16Le));
        totalContent.replace(umdNewLine, utf16LeNewLine);
        result = storeUncompressedContent(totalContent);
        if (result) {
            result = analyzeUncompressedFile();
        }
    }
    return result;
}

bool RUmdFileModel::analyzeUncompressedFile() {
    bool result = false;
    m_file_line_pos.clear();
    int lineNum = 0;
    if (m_uncompressed_file->isReadable()) {
        //seek to the beginning of file
        if (m_uncompressed_file->seek(0)) {
            //System codec could not use QTextStream to get pos
            qDebug()<<"Analyse with file"<<m_uncompressed_file->fileName();
            qDebug()<<"currentThreadId"<<QThread::currentThreadId();
            //Use file to read each line and store the postion
            int fetchMore;
            qDebug()<<"m_codec:"<<(quint32)m_codec;
            newLineCharLaw(*m_codec, fetchMore);
            m_file_line_pos[0] = 0;
            QByteArray array = m_uncompressed_file->readLine();
            lineNum = 1;

            while (!array.isNull()) {
                if (fetchMore > 0) {
                    array.append(m_uncompressed_file->read(fetchMore));
                }

                //For some codec like UTF16 the read line may not really a line
                if (lastCharIsNewLine(array, *m_codec)) {
                    m_file_line_pos[lineNum] = m_uncompressed_file->pos();
                    if (lineNum<100) {
                        qDebug()<<"Store lineNum"<<lineNum
                               <<"pos"<<m_file_line_pos[lineNum];
                    }
                    m_uncompressed_file->seek(m_file_line_pos[lineNum]);
                    ++lineNum;
                }
                else {
                    // qDebug()<<"last char is not new line, continue reading";
                }

                array = m_uncompressed_file->readLine();
            }

            result = true;
        }
        else {
            qDebug()<<"ERROR::Could not seek file to 0";
        }
    }
    else {
        qDebug()<<"ERROR::file is not readable";
    }
    return result;
}

bool RUmdFileModel::storeUncompressedContent(QByteArray &content) {
    bool result = false;
    if (content.length()) {
        QString storeFileName = m_engine.uncompressedContentFileName(m_file->fileName());
        //For safty
        if (m_uncompressed_file) {
            m_uncompressed_file->close();
            delete m_uncompressed_file;
        }

        m_uncompressed_file = new QFile(storeFileName);
        result = openFile(storeFileName, m_uncompressed_file, QIODevice::ReadWrite | QIODevice::Truncate);
        if (result && m_uncompressed_file) {
            m_uncompressed_file->write(content);
            m_uncompressed_file->flush();
            result = true;
        }
        else {
            qDebug()<<"ERROR::Open uncompressedContentFile "<<storeFileName<<" failed";
        }
    }
    return result;
}

bool RUmdFileModel::processChapterOffset() {
    quint32 offset;
    qDebug()<<"processChapterOffset";
    if (m_chapter_offset_value && m_data.contains(m_chapter_offset_value)) {
        QByteArray content = m_data[m_chapter_offset_value];
        for (int i = 0; i < content.length()/KUmdDataChapterOffLen; i++) {
            offset = m_engine.byteArrayToUint(content.mid(i*KUmdDataChapterOffLen, KUmdDataChapterOffLen));
            m_file_chapter_offset.append(offset);
        }
    }
    return true;
}

bool RUmdFileModel::processChapterString() {
    quint32 chapterLength;
    QString chapter;
    qDebug()<<"processChapterString";
    if (m_chapter_value && m_data.contains(m_chapter_value)) {
        QByteArray content = m_data[m_chapter_value];
        for (int i = 0; i <content.length();) {
            chapterLength = m_engine.byteArrayToUint(content.mid(i, KUmdDataChapterLengthLen));
            i += KUmdDataChapterLengthLen;
            chapter = m_codec->toUnicode(content.mid(i, chapterLength));
            i += chapterLength;
            m_file_chapter.append(chapter);
        }
    }
    return true;
}

bool RUmdFileModel::openFile(const QString& fileName, QFile *& file, QIODevice::OpenMode mode) {
    bool success(false);
    qDebug()<<"RFileModel::Opening file:"<<fileName;
    if (file) {
        file->close();
        delete file;
        file = NULL;
    }

    file = new QFile(fileName);
    success = file->open(mode);
    if (success) {
        qDebug()<<"File opened";
        m_open_result = REngine::OpenFileSuccess;
    }
    else {
        qDebug()<<"RFileModel::Open file failed:"<<m_file->errorString();
        delete file;
        file = NULL;
        m_open_result = REngine::OpenFileFailed;
    }
    return success;
}

bool RUmdFileModel::initFileInfo() {
    //Use analyze to get info
    return analyzeFile();
}

bool RUmdFileModel::openFile(const QString& fileName) {
    m_file_line_pos.clear();
    m_file_analyzed = false;
    bool success(false);
    //Umd always uses UTF-16LE codec
    m_codec = QTextCodec::codecForName(KCodecUTF16LE.toLatin1());

    success = openFile(fileName, m_file, QIODevice::ReadOnly);
    if (success) {
        QString ucomFileName = m_engine.uncompressedContentFileName(fileName);
        QFileInfo fileInfo(ucomFileName);
        if (fileInfo.exists()) {
            //Only open when exist
            qDebug()<<"Openning uncompressed file:"<<ucomFileName;
            success = openFile(ucomFileName, m_uncompressed_file, QIODevice::ReadOnly);
            if (success) {
                m_open_result = REngine::OpenFileSuccess;
            }
            else {
                qDebug()<<"RFileModel::Open uncompressed file failed:"<<m_uncompressed_file->errorString();
                m_open_result = REngine::OpenFileFailed;
            }
        }
        else {
            qDebug()<<"uncompressed file is not exist:"<<ucomFileName;
        }
        //For UMD file, we need to analyze file to get file info, like author, chapters
    }
    else {
        qDebug()<<"RFileModel::Open file failed:"<<m_file->errorString();
        m_open_result = REngine::OpenFileFailed;
    }

    return success;
}


bool RUmdFileModel::saveFileLinePosition() {
    QList<int> chapterLines;
    m_engine.saveFileLinePosition(m_file->fileName(), m_file_line_pos, chapterLines);
    return true;
}

bool RUmdFileModel::readFileLinePosition() {
    QList<int> chapterLines;
    return m_engine.fileLinePosition(m_file->fileName(), m_file_line_pos, chapterLines);
}

QVariant RUmdFileModel::data(int row) const {
    if (row < m_file_line_pos.count()) {
        if (m_uncompressed_file) {
            if (m_uncompressed_file->seek(m_file_line_pos[row])) {
                QTextStream stream(m_uncompressed_file);
                stream.setCodec(m_codec);
                stream.setAutoDetectUnicode(true);
                QString str = stream.readLine(KLineMaxLength);
                if (str.length() || !m_uncompressed_file->atEnd()) {
                    qDebug()<<"Read str row"<<row
                           <<"pos"<<m_file_line_pos[row]
                          <<"length"<<str.length()<<str;
                    return str+KNewLine;
                }
                else {
                    qDebug()<<"read file line failed, line number"<<row
                           <<"Position"<<m_file_line_pos[row]
                          <<m_uncompressed_file->errorString();
                }
            }
            else {
                qDebug()<<"could not seek to position/line"<<m_file_line_pos[row]<<row;
            }
        }
        else {
            qDebug()<<"ERROR::uncompressed file is not opened!";
        }
    }
    else {
        qDebug()<<"row number is too large:row/max"<<row<<m_file_line_pos.count();
    }
    return QString(KNewLine);
}

void RUmdFileModel::chapters(QStringList& chapterList) {
    chapterList.clear();
    chapterList<<m_file_chapter;
}

int RUmdFileModel::lineNumberOfChapter(int chapterIndex) {
    qint64 offset;
    int lineNumber = KInvalidId;
    if (chapterIndex >= 0 && chapterIndex < m_file_chapter_offset.count()) {
        offset = m_file_chapter_offset[chapterIndex];
        for (QHash<int, qint64>::iterator i = m_file_line_pos.begin(); i != m_file_line_pos.end(); i++) {
            if (i.value() >= offset) {
                lineNumber = i.key();
                break;
            }
        }
    }
    return lineNumber;
}

int RUmdFileModel::currentChapterIndex() {
    int lineNum = m_engine.lastLineNumber(m_file->fileName());
    int index = -1;
    if (m_file_line_pos.contains(lineNum)) {
        qint64 offset = m_file_line_pos[lineNum];
        for (int i = 0; i < m_file_chapter_offset.count(); i++) {
            if (m_file_chapter_offset[i] >= offset) {
                index = i-1 >= 0 ? i-1 : 0;
                break;
            }
        }
    }
    return index;
}
